package com.nts.visionworks.core.multipart;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.net.SocketException;
import java.net.URLEncoder;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.servlet.view.AbstractView;

import com.nts.visionworks.core.multipart.domain.Archive;

/**
 * 파일 다운로드를 위한 스트림 뷰를 정의(스프링 추상 뷰 재정의)한다.
 * @author Devcken
 */
public class OutputFileStreamView extends AbstractView
{
	private static final Logger logger = LoggerFactory.getLogger(OutputFileStreamView.class);
	
	/**
	 * 아카이브 아이디 파라메터 명칭을 나타낸다.
	 */
	public static final String _ARCHIVE_ID_PARAMETER_NAME = "archiveId";
	
	/**
	 * 아카이브 파라메터 명칭을 나타낸다.
	 */
	public static final String _ARCHIVE_PARAMETER_NAME = "archive";
	
	/**
	 * 업로드 루트 파라메터 명칭을 나타낸다.
	 */
	public static final String _UPLOAD_ROOT_PARAMETER_NAME = "uploadRoot";
	
	public OutputFileStreamView()
	{
		// 기본 컨텐츠 타입(MIME)을 설정한다.
		super.setContentType("application/octet-stream");
	}

	/**
	 * 다운로드 요청에 대한 파일을 출력 스트림으로 내보낸다.
	 * @param model 뷰로 전달되는 파라메터 목록으로 아카이브 아이디와 업로드 최상위 경로가 전달된다.
	 * @param request HTTP 요청이 전달된다.
	 * @param response HTTP 응답이 전달된다.
	 */
	@Override
	protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response)	throws Exception
	{
		// 아카이브 파라메터 명칭에 맞는 아카이브 인스턴스를 가져온다.
		Archive archive = (Archive)model.get(_ARCHIVE_PARAMETER_NAME);
		
		// 아카이브 인스턴스가 존재하지 않는 경우, 다운로드를 취소한다.
		if (archive == null)
		{
			logger.error("요청된 아카이브가 존재하지 않습니다.(아카이브 아이디 : {})", model.get(_ARCHIVE_ID_PARAMETER_NAME));
			
			return;
		}
		
		// 헤더에 파일의 크기를 설정한다. 파일의 크기가 정수형의 범위를 벗어날 수도 있으므로 문자열로 설정한다.
		response.setHeader("Content-Length", "" + archive.getFileSize());
		
		// 컨텐츠 타입(MIME)을 설정한다.
		response.setContentType(archive.getContentType());
		
		// 다운로드 시에 다운로드 받는 파일의 명칭을 UTF-8로 변환해준다.
		// 이렇게 하는 이유는 다운로드 받는 대상 어플리케이션이 UTF-8만을 지원하는 경우 다운로드가 거부될 수도 있기 때문이다.
		response.setHeader("Content-Disposition",
				"attachment;fileName=\"" + URLEncoder.encode(archive.getOriginalName().replaceAll(" ", "-"), "UTF-8") + "\";");
		
		// 업로드 최상위 경로를 가져온다.
		String uploadRoot = model.get(_UPLOAD_ROOT_PARAMETER_NAME).toString();
		
		// 파일의 실제 경로(서버 측 로컬 경로)를 가져온다. 업로드된 파일은 마스크된 이름으로 저장된다.
		String filePath = uploadRoot + archive.getRelativePath() + archive.getMaskedName();
		
		OutputStream outputStream = response.getOutputStream();
		FileInputStream fileStream = null;
		
		try
		{
			fileStream = new FileInputStream(filePath);
			
			// HTTP 응답의 출력 스트림에 파일 스트림을 쓴다.
			FileCopyUtils.copy(fileStream, outputStream);
		}
		catch (FileNotFoundException error)
		{
			logger.error("다운로드하려는 파일({})이 존재하지 않습니다.", filePath);
		}
		catch (SocketException error)
		{
			// 소켓 예외인 경우 파일 다운로드가 중지됐을 때 발생합니다.
			if (logger.isDebugEnabled())
			{
				logger.debug("파일 스트림 복사가 중지되었습니다.");
			}
		}
		catch (IOException error)
		{
			logger.error("파일 스트림 복사에 실패했습니다.", error);
		}
		finally
		{
			if (fileStream != null)
			{
				fileStream.close();
			}
			
			if (outputStream != null)
			{
				outputStream.flush();
				outputStream.close();
			}
		}
	}
}